﻿
//	------------------------------------------------------------------------------
//                                       _                          
//              _ __ ___   ___ _ __ ___ | |__  _ __ __ _ _ __   ___ 
//             | '_ ` _ \ / _ \ '_ ` _ \| '_ \| '__/ _` | '_ \ / _ \
//             | | | | | |  __/ | | | | | |_) | | | (_| | | | |  __/
//             |_| |_| |_|\___|_| |_| |_|_.__/|_|  \__,_|_| |_|\___|
//                                                     
//	------------------------------------------------------------------------------

#region - License -
//
//	------------------------------------------------------------------------------                                                   
//	         Copyright (c) 2010 Peter Vyvey (peter.vyvey@chiliware.be)
//	------------------------------------------------------------------------------
//
//
//	This file is part of CHILIWARE MEMBRANE.
//
//	CHILIWARE MEMBRANE is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	CHILIWARE MEMBRANE is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with CHILIWARE MEMBRANE.  If not, see <http://www.gnu.org/licenses/>.
//
//
#endregion

#region - Changes -
//
//	peter.vyvey@chiliware.be		2010-08-29		Created
//
#endregion

#region - Using -

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

#endregion

namespace Chiliware.Membrane.Entity
{
	#region - ComparableObject -

	/// <summary>
	/// Provides a standard base class for facilitating comparison of objects.
	/// <para>
	/// This class is inspired by the discussions about Equals and GetHashCode at 
	/// http://devlicio.us/blogs/billy_mccafferty/archive/2007/04/25/using-equals-gethashcode-effectively.aspx and 
	/// http://groups.google.com/group/sharp-architecture/browse_thread/thread/f76d1678e68e3ece?hl=en.
	/// </para>
	/// </summary>
	[Serializable]
	public abstract class ComparableObject
	{
		#region - Constants & static fields -

		/// <summary>
		/// To help ensure hashcode uniqueness, a carefully selected random number multiplier 
		/// is used within the calculation.  Goodrich and Tamassia's Data Structures and
		/// Algorithms in Java asserts that 31, 33, 37, 39 and 41 will produce the fewest number
		/// of collissions. 
		/// <para>
		/// See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ for more information.
		/// </para>
		/// </summary>
		private static readonly int DEFAULT_PRIME_NUMBER = 31;

		#endregion

		#region - Private static fields -

		/// <summary>
		/// This static member caches the domain signature properties to avoid looking them up for 
		/// each instance of the same type.
		/// 
		/// A description of the very slick ThreadStatic attribute may be found at 
		/// http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2005/08/18/132026.aspx
		/// </summary>
		private static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> signatureCache;

		#endregion

		#region - Public methods -

		/// <summary>
		/// Determines whether the specified System.Object is equal to the current System.Object.
		/// </summary>
		/// <param name="obj">The object to compare with the current object.</param>
		/// <returns>TRUE if the objects are equal, FALSE otherwise.</returns>
		public override bool Equals(object obj)
		{
			bool result = false;
			ComparableObject compareTo = obj as ComparableObject;

			if (Object.ReferenceEquals(this, compareTo))
			{
				result = true;
			}
			else
			{
				result = compareTo != null && this.HasSameObjectSignatureAs(compareTo);
			}

			return result;
		}

		/// <summary>
		/// Used to provide the hashcode identifier of an object using the signature 
		/// properties of the object; although it's necessary for NHibernate's use, this can 
		/// also be useful for business logic purposes and has been included in this base 
		/// class, accordingly.  Since it is recommended that GetHashCode change infrequently, 
		/// if at all, in an object's lifetime; it's important that properties are carefully
		/// selected which truly represent the signature of an object.
		/// </summary>
		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = base.GetHashCode();
				IEnumerable<PropertyInfo> signatureProperties = this.GetSignatureProperties();

				if (signatureProperties.Any())
				{
					// It's possible for two objects to return the same hash code based on 
					// identically valued properties, even if they're of two different types, 
					// so we include the object's type in the hash calculation
					hashCode = this.GetType().GetHashCode();

					foreach (PropertyInfo property in signatureProperties)
					{
						object value = property.GetValue(this, null);

						if (value != null)
						{
							hashCode = (hashCode * DEFAULT_PRIME_NUMBER) ^ value.GetHashCode();
						}
					}
				}

				// If no properties were flagged as being part of the signature of the object,
				// then simply return the hashcode of the base object as the hashcode.
				return hashCode;
			}
		}

		/// <summary>
		/// Return the properties having a specific signature attribute.
		/// </summary>
		public virtual IEnumerable<PropertyInfo> GetSignatureProperties()
		{
			IEnumerable<PropertyInfo> properties;

			if (signatureCache == null)
			{
				signatureCache = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
			}

			if (!ComparableObject.signatureCache.TryGetValue(this.GetType(), out properties))
			{
				ComparableObject.signatureCache[this.GetType()] = properties = this.GetTypeSpecificSignatureProperties();
			}

			return properties;
		}

		#endregion

		#region - Private & protected methods -

		/// <summary>
		/// You may override this method to provide your own comparison routine.
		/// </summary>
		protected virtual bool HasSameObjectSignatureAs(ComparableObject compareTo)
		{
			IEnumerable<PropertyInfo> signatureProperties = this.GetSignatureProperties();

			foreach (PropertyInfo property in signatureProperties)
			{
				object valueOfThisObject = property.GetValue(this, null);
				object valueToCompareTo = property.GetValue(compareTo, null);

				if (valueOfThisObject == null && valueToCompareTo == null)
					continue;

				if ((valueOfThisObject == null ^ valueToCompareTo == null) || (!valueOfThisObject.Equals(valueToCompareTo)))
				{
					return false;
				}
			}

			// If we've gotten this far and signature properties were found, then we can
			// assume that everything matched; otherwise, if there were no signature 
			// properties, then simply return the default bahavior of Equals
			return signatureProperties.Any() || base.Equals(compareTo);
		}

		/// <summary>
		/// Enforces the template method pattern to have child objects determine which specific properties should and should not be included in the object signature comparison.  
		/// </summary>
		/// /// <returns>A collection of <see cref="PropertyInfo"/> object with the comparable properties.</returns>
		protected abstract IEnumerable<PropertyInfo> GetTypeSpecificSignatureProperties();

		#endregion
	}

	#endregion
}
